package io.modifier.basic.core.data.kvCache.property

import android.os.Build
import androidx.annotation.RequiresApi
import io.modifier.basic.core.data.kvCache.IKVOwner
import io.modifier.basic.core.data.kvCache.cache.ICache
import java.util.function.BiFunction
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

class KVMapProperty<V>(
    private val kvProperty: KVProperty<V>
) : ReadOnlyProperty<IKVOwner, MutableMap<String, V>> {
    private var cache: KVMap<V>? = null

    override fun getValue(thisRef: IKVOwner, property: KProperty<*>): MutableMap<String, V> =
        cache?.updateValues() ?: KVMap(thisRef.kv, property.name, kvProperty.decode, kvProperty.encode).also { cache = it }
}

class KVMap<V>(
    private val kv: ICache,
    private val propertyName: String,
    private val decode: (String) -> V,
    private val encode: Pair<String, V>.() -> Boolean,
    private val map: MutableMap<String, V> = mutableMapOf()
) : MutableMap<String, V> by map {
    private val keysName = "$propertyName\$key"

    init {
        updateValues()
    }

    fun updateValues() = apply {
        map.clear()
        keys.forEach { key ->
            map[key] = decode(key.addPrefix())
        }
    }

    private fun String.addPrefix() = "$propertyName$$$this"

    override fun get(key: String): V? = decode(key.addPrefix())

    override fun put(key: String, value: V): V? =
        map.put(key, value).also {
            encode(key.addPrefix() to value)
            kv.put(keysName, keys + key)
        }

    override fun putAll(from: Map<out String, V>) =
        map.putAll(from).also {
            from.forEach { (key, value) -> encode(key.addPrefix() to value) }
            kv.put(keysName, keys + from.keys)
        }

    @RequiresApi(Build.VERSION_CODES.N)
    override fun putIfAbsent(key: String, value: V): V? {
        val isAbsent = map.containsKey(key).not()
        return map.putIfAbsent(key, value).also {
            if (isAbsent) {
                encode(key.addPrefix() to value)
                kv.put(keysName, keys + key)
            }
        }
    }

    @RequiresApi(Build.VERSION_CODES.N)
    override fun merge(key: String, value: V & Any, remappingFunction: BiFunction<in V & Any, in V & Any, out V?>): V? =
        map.merge(key, value, remappingFunction).also {
            if (it != null) {
                encode(key.addPrefix() to it)
                kv.put(keysName, keys + key)
            }
        }

    override fun remove(key: String): V? =
        map.remove(key).also {
            kv.remove(key.addPrefix())
            kv.put(keysName, keys - key)
        }

    @RequiresApi(Build.VERSION_CODES.N)
    override fun remove(key: String, value: V): Boolean =
        map.remove(key, value).also { removed ->
            if (removed) {
                kv.remove(key.addPrefix())
                kv.put(keysName, keys - key)
            }
        }

    @RequiresApi(Build.VERSION_CODES.N)
    override fun replace(key: String, value: V): V? =
        map.replace(key, value).also {
            if (containsKey(key)) encode(key.addPrefix() to value)
        }

    @RequiresApi(Build.VERSION_CODES.N)
    override fun replace(key: String, oldValue: V, newValue: V): Boolean =
        map.replace(key, oldValue, newValue).also { replaced ->
            if (replaced) encode(key.addPrefix() to newValue)
        }

    @RequiresApi(Build.VERSION_CODES.N)
    override fun replaceAll(function: BiFunction<in String, in V, out V>) =
        map.replaceAll { key, value ->
            function.apply(key, value)
                .also { encode(key.addPrefix() to it) }
        }

    override fun clear() {
        keys.forEach { key -> kv.remove(key.addPrefix()) }
        kv.remove(keysName)
        map.clear()
    }

    override val keys: MutableSet<String>
        get() = kv.getStringSet(keysName, null).orEmpty().toMutableSet()

    override val size: Int
        get() = keys.size

    override val entries: MutableSet<MutableMap.MutableEntry<String, V>>
        get() {
            val entries = map.entries
            return object : MutableSet<MutableMap.MutableEntry<String, V>> by entries {

                override fun iterator(): MutableIterator<MutableMap.MutableEntry<String, V>> {
                    val iterator = entries.iterator()
                    return object : MutableIterator<MutableMap.MutableEntry<String, V>> by iterator {
                        private var lastKey: String? = null

                        override fun remove() =
                            iterator.remove().also {
                                lastKey?.let { key ->
                                    kv.remove(key.addPrefix())
                                    kv.put(keysName, keys - key)
                                }
                            }

                        override fun next(): MutableMap.MutableEntry<String, V> {
                            val entry = iterator.next()
                            lastKey = entry.key
                            return object : MutableMap.MutableEntry<String, V> by entry {
                                override fun setValue(newValue: V): V =
                                    entry.setValue(newValue).also { encode(key.addPrefix() to newValue) }
                            }
                        }
                    }
                }
            }
        }

    override fun toString() = map.toString()

    override fun hashCode() = map.hashCode()

    override fun equals(other: Any?) = map == other
}